package cn.iocoder.yudao.module.product.category.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.product.category.dal.dataobject.ProductCategoryDO;
import cn.iocoder.yudao.module.product.category.dal.mysql.ProductCategoryMapper;
import cn.iocoder.yudao.module.product.category.vo.ProductCategoryListReqVO;
import cn.iocoder.yudao.module.product.category.vo.ProductCategoryPageReqVO;
import cn.iocoder.yudao.module.product.category.vo.ProductCategorySaveReqVO;
import cn.iocoder.yudao.module.product.spu.service.ProductSpuService;
import jakarta.annotation.Nullable;
import jakarta.annotation.Resource;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import java.util.*;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserLastShopId;
import static cn.iocoder.yudao.module.api.product.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.product.category.dal.dataobject.ProductCategoryDO.PARENT_ID_FIRST_LEVEL;

/**
 * 产品分类 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
public class ProductCategoryServiceImpl implements ProductCategoryService {
    private static final String PRODUCT_CATEGORY_ALL_LIST = "xianmin:product:category_all";
    private static final String PRODUCT_CATEGORY_DESCENDENT_ID_LIST = "xianmin:product:category_descendent_ids";

    @Resource
    private ProductCategoryMapper productCategoryMapper;

    @Resource
    @Lazy // 循环依赖，避免循环依赖
    private ProductSpuService productSpuService;

    @CacheEvict(cacheNames = {PRODUCT_CATEGORY_ALL_LIST, PRODUCT_CATEGORY_DESCENDENT_ID_LIST},
            allEntries = true) // allEntries 清空所有缓存，因为操作一个类，涉及到多个子类缓存
    @Override
    public Long createCategory(ProductCategorySaveReqVO createReqVO) {
        // 校验父分类存在
        ProductCategoryDO parent = validateParentProductCategory(PARENT_ID_FIRST_LEVEL, createReqVO.getParentId());

        // 插入
        ProductCategoryDO category = BeanUtils.toBean(createReqVO, ProductCategoryDO.class);
        if (parent != null) {
            category.setShopId(parent.getShopId());
        } else {
            category.setShopId(getLoginUserLastShopId());
        }
        productCategoryMapper.insert(category);
        // 返回
        return category.getId();
    }

    @CacheEvict(cacheNames = {PRODUCT_CATEGORY_ALL_LIST, PRODUCT_CATEGORY_DESCENDENT_ID_LIST},
            allEntries = true) // allEntries 清空所有缓存，因为操作一个类，涉及到多个子类缓存
    @Override
    public void updateCategory(ProductCategorySaveReqVO updateReqVO) {
        // 校验分类是否存在
        validateProductCategoryExists(updateReqVO.getId());
        // 校验父分类存在
        ProductCategoryDO parent = validateParentProductCategory(updateReqVO.getId(), updateReqVO.getParentId());

        // 更新
        ProductCategoryDO updateObj = BeanUtils.toBean(updateReqVO, ProductCategoryDO.class);
        if (parent != null) {
            updateObj.setShopId(parent.getShopId());
        }
        productCategoryMapper.updateById(updateObj);
    }

    @CacheEvict(cacheNames = {PRODUCT_CATEGORY_ALL_LIST, PRODUCT_CATEGORY_DESCENDENT_ID_LIST},
            allEntries = true) // allEntries 清空所有缓存，因为操作一个类，涉及到多个子类缓存
    @Override
    public void deleteCategory(Long id) {
        // 校验是否还有子分类
        if (productCategoryMapper.selectCountByParentId(id) > 0) {
            throw exception(CATEGORY_EXISTS_CHILDREN);
        }
        // 校验分类是否绑定了 SPU
        Long spuCount = productSpuService.getSpuCountByCategoryId(id);
        if (spuCount > 0) {
            throw exception(CATEGORY_HAVE_BIND_SPU);
        }
        // 删除
        productCategoryMapper.deleteById(id);
    }

    private ProductCategoryDO validateParentProductCategory(Long id, Long parentId) {
        // 父分类不能是根分类
        if (parentId < PARENT_ID_FIRST_LEVEL) {
            parentId = PARENT_ID_FIRST_LEVEL;
        }
        if (Objects.equals(parentId, PARENT_ID_FIRST_LEVEL)) {
            return null;
        }
        // 父分类不能是自己
        if (id > 0 && Objects.equals(id, parentId)) {
            throw exception(CATEGORY_PARENT_CAN_NOT_SELF);
        }
        // 父分类不存在
        ProductCategoryDO category = productCategoryMapper.selectById(parentId);
        if (category == null) {
            throw exception(CATEGORY_PARENT_NOT_EXISTS);
        }
        return category;
    }

    private ProductCategoryDO validateProductCategoryExists(Long id) {
        ProductCategoryDO category = productCategoryMapper.selectById(id);
        if (category == null) {
            throw exception(CATEGORY_NOT_EXISTS);
        }
        return category;
    }

    @Override
    public void validateCategoryList(Set<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        // 获得产品分类信息
        List<ProductCategoryDO> list = productCategoryMapper.selectByIds(ids);
        Map<Long, ProductCategoryDO> categoryMap = CollectionUtils.convertMap(list, ProductCategoryDO::getId);
        // 校验
        ids.forEach(id -> {
            ProductCategoryDO category = categoryMap.get(id);
            if (category == null) {
                throw exception(CATEGORY_NOT_EXISTS);
            }
            if (!CommonStatusEnum.ENABLE.getStatus().equals(category.getStatus())) {
                throw exception(CATEGORY_DISABLED, category.getName());
            }
        });
    }

    @Override
    public PageResult<ProductCategoryDO> getCategoryPage(ProductCategoryPageReqVO reqVO) {
        return productCategoryMapper.selectPage(reqVO);
    }

    @Override
    public ProductCategoryDO getCategory(Long id) {
        return productCategoryMapper.selectById(id);
    }

    @Override
    public void validateCategory(Long id, Integer level) {
        if (id == null || id <= 0) {
            // 不强制校验，因为店铺可能未设置产品分类
            return;
        }
        ProductCategoryDO category = productCategoryMapper.selectById(id);
        if (category == null) {
            throw exception(CATEGORY_NOT_EXISTS);
        }
        // 允许选择已禁用掉的分类
        //if (Objects.equals(category.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
        //    throw exception(CATEGORY_DISABLED, category.getName());
        //}
        // 允许选择非顶级的分类
        //if (getCategoryLevel(id) < level) {
        //    throw exception(SPU_SAVE_FAIL_CATEGORY_LEVEL_ERROR, level);
        //}
    }

    @Override
    public Integer getCategoryLevel(Long id) {
        if (Objects.equals(id, PARENT_ID_FIRST_LEVEL)) {
            return 0;
        }
        int level = 1;
        // for 的原因，是因为避免脏数据，导致可能的死循环。一般不会超过 100 层哈
        for (int i = 0; i < Byte.MAX_VALUE; i++) {
            // 如果没有父节点，break 结束
            ProductCategoryDO category = productCategoryMapper.selectById(id);
            if (category == null
                || Objects.equals(category.getParentId(), PARENT_ID_FIRST_LEVEL)) {
                break;
            }
            // 继续递归父节点
            level++;
            id = category.getParentId();
        }
        return level;
    }

    @Override
    public List<ProductCategoryDO> getCategoryList(ProductCategoryListReqVO listReqVO) {
        return productCategoryMapper.selectList(listReqVO);
    }

    @DataPermission(enable = false) // 禁用数据权限，避免建立不正确的缓存
    @Cacheable(cacheNames = PRODUCT_CATEGORY_ALL_LIST)
    @Override
    public List<ProductCategoryDO> getAllCategoryList() {
        return productCategoryMapper.selectAll();
    }

    @Override
    public List<ProductCategoryDO> getDescendentCategoryList(@Nullable Long shopId, @Nullable Set<Long> parentIds, @Nullable Integer status) {
        if (CollUtil.isEmpty(parentIds)) {
            return productCategoryMapper.selectListByStatus(shopId, status);
        }
        List<ProductCategoryDO> children = new LinkedList<>();
        List<ProductCategoryDO> allCategory = SpringUtil.getBean(ProductCategoryService.class).getAllCategoryList();
        if (CollUtil.isEmpty(allCategory)) {
            return children;
        }
        for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下，存在死循环
            // 查询当前层，所有的子类目
            List<ProductCategoryDO> categories = new ArrayList<>();
            for (ProductCategoryDO category : allCategory) {
                if (parentIds.contains(category.getParentId())) {
                    if (status == null || Objects.equals(category.getStatus(), status)) {
                        categories.add(category);
                    }
                }
            }
            // 1. 如果没有子类目，则结束遍历
            if (CollUtil.isEmpty(categories)) {
                break;
            }
            // 2. 如果有子类目，继续遍历
            children.addAll(categories);
            parentIds = convertSet(categories, ProductCategoryDO::getId);
        }
        return children;
    }

    @DataPermission(enable = false) // 禁用数据权限，避免建立不正确的缓存
    @Cacheable(cacheNames = PRODUCT_CATEGORY_DESCENDENT_ID_LIST, key = "#parentId-#status")
    @Override
    public Set<Long> getDescendentCategoryIdsFromCache(Long parentId, Integer status) {
        List<ProductCategoryDO> children = getDescendentCategoryList(null, CollUtil.newHashSet(parentId), status);
        return convertSet(children, ProductCategoryDO::getId);
    }

    @Override
    public List<ProductCategoryDO> getEnableCategoryList(Long shopId, Set<Long> parentIds) {
        return productCategoryMapper.selectListByParentIdsAndStatus(shopId, parentIds, CommonStatusEnum.ENABLE.getStatus());
    }

    @Override
    public List<ProductCategoryDO> getEnableCategoryList(Set<Long> ids) {
        return productCategoryMapper.selectListByIdAndStatus(ids, CommonStatusEnum.ENABLE.getStatus());
    }

}
